//go:build windows
// +build windows

package win系统类

import (
	"errors"
	"fmt"
	扩展类 "gitee.com/go_888/extends/extend"
	"gitee.com/go_888/extends/windows/internal/windows"
	内部类2 "gitee.com/go_888/extends/windows/internal/windows"
	进程类 "gitee.com/go_888/extends/windows/process"
	注册表类 "gitee.com/go_888/extends/windows/registry"
	"github.com/go-ole/go-ole"
	"github.com/go-ole/go-ole/oleutil"
	"golang.org/x/sys/windows"
	"golang.org/x/sys/windows/registry"
	"os/exec"
	"strings"
	"syscall"
	"unicode/utf16"
	"unsafe"

	"log"
	"os"
)

// X快捷方式取路径 通过WScript.Shell对象获取快捷方式的目标路径。
// 参数:
// 路径 - 快捷方式的路径。
// 返回值:
// 目标路径 - 快捷方式指向的实际路径。
// error - 如果操作失败，返回错误信息。
func X快捷方式取路径(lnk路径 string) (string, error) {
	// 检查路径是否存在
	_, err := os.Stat(lnk路径)
	if err != nil {
		return "", errors.New("lnk路径:" + lnk路径 + " ,文件不存在")
	}

	// 初始化COM库
	ole.CoInitialize(0)
	defer ole.CoUninitialize()

	// 创建WScript.Shell对象
	oleShellObject, err1 := oleutil.CreateObject("WScript.Shell")
	if err1 != nil {
		return "", err1
	}

	// 获取WScript.Shell对象的IDispatch接口
	wshell, err2 := oleShellObject.QueryInterface(ole.IID_IDispatch)
	if err2 != nil {
		return "", err2
	}

	// 通过CreateShortcut方法创建快捷方式对象
	cs, err3 := oleutil.CallMethod(wshell, "CreateShortcut", lnk路径)
	if err3 != nil {
		return "", err3
	}

	// 将快捷方式对象转换为IDispatch接口
	idispatch := cs.ToIDispatch()

	// 获取快捷方式对象的TargetPath属性，即快捷方式指向的目标路径
	返回值, err4 := oleutil.GetProperty(idispatch, "TargetPath")
	if err4 != nil {
		return "", err4
	}

	// 返回快捷方式的目标路径
	return 返回值.ToString(), nil
}

// X创建快捷方式 用于创建一个指向特定目标路径的快捷方式（.lnk文件）
// 参数:
// - 目标路径: 希望创建快捷方式指向的文件或文件夹的路径
// - lnk路径: 计划创建的快捷方式的路径和文件名, 如:"C:\Git\git.lnk"
// 返回值:
// - error: 如果在创建快捷方式过程中发生错误，则返回该错误
func X创建快捷方式(目标路径, lnk路径 string) error {
	// 初始化COM库，使用线程公寓模型和速度优先的初始化标志
	ole.CoInitializeEx(0, ole.COINIT_APARTMENTTHREADED|ole.COINIT_SPEED_OVER_MEMORY)
	// 确保在函数结束时释放COM资源
	defer ole.CoUninitialize()

	// 创建WScript.Shell对象，用于后续创建快捷方式
	oleShellObject, err := oleutil.CreateObject("WScript.Shell")
	if err != nil {
		// 如果创建失败，记录错误并返回
		log.Printf("创建WScript失败,WScript.Shell: %v", err)
		return err
	}
	// 确保在函数结束时释放WScript.Shell对象
	defer oleShellObject.Release()

	// 获取WScript.Shell对象的IDispatch接口
	wshell, err := oleShellObject.QueryInterface(ole.IID_IDispatch)
	if err != nil {
		// 如果获取接口失败，记录错误并返回
		log.Printf("查询IDispatch接口失败: %v", err)
		return err
	}
	// 确保在函数结束时释放IDispatch接口
	defer wshell.Release()

	// 调用WScript.Shell对象的CreateShortcut方法创建快捷方式对象
	cs, err := oleutil.CallMethod(wshell, "CreateShortcut", lnk路径)
	if err != nil {
		// 如果创建快捷方式对象失败，记录错误并返回
		log.Printf("创建快捷方式失败: %v", err)
		return err
	}

	// 将快捷方式对象转换为IDispatch接口
	idispatch := cs.ToIDispatch()
	// 设置快捷方式的目标路径
	_, err = oleutil.PutProperty(idispatch, "TargetPath", 目标路径)
	if err != nil {
		// 如果设置目标路径失败，记录错误并返回
		log.Printf("设置TargetPath失败: %v", err)
		return err
	}

	// 调用快捷方式对象的Save方法保存快捷方式
	_, err = oleutil.CallMethod(idispatch, "Save")
	if err != nil {
		// 如果保存快捷方式失败，记录错误并返回
		log.Printf("保存快捷方式失败: %v", err)
		return err
	}

	// 如果一切成功，返回nil
	return nil
}

// X创建桌面快捷方式 在桌面上创建指定目标路径的快捷方式。
// 参数:
// 目标路径 - 要创建快捷方式指向的目标文件或文件夹的路径。
// lnk文件名 - 可选参数，用于指定快捷方式文件的名称。如果未提供，则默认使用目标路径的文件名。
// 返回值:
// error - 如果在创建快捷方式过程中发生错误，则返回错误信息；否则返回nil。
func X创建桌面快捷方式(目标路径 string, lnk文件名 string) error {
	lnk路径 := X取桌面目录() + "\\" + lnk文件名
	if 扩展类.X取文本右边(lnk路径, 4) != ".lnk" {
		lnk路径 = lnk路径 + ".lnk"
	}

	return X创建快捷方式(目标路径, lnk路径)
}

// 系统_获取特定目录通过指定的标识符获取系统的特定目录路径。
// 该函数使用Windows API调用 sHGetSpecialFolderPathW 来获取路径。
// 参数:
// - X常量_系统目录_ - 用于指定需要获取的特定目录的标识符
// - 更多参考 https://learn.microsoft.com/zh-cn/windows/win32/api/shldisp/ne-shldisp-shellspecialfolderconstants?redirectedfrom=MSDN
// 返回值:
// - 如果成功获取到路径，则返回该路径字符串；否则返回空字符串。
func X获取特定目录(X常量_系统目录_ int) string {
	// 定义一个存放路径的数组，大小为Windows路径的最大长度。
	var path [260]uint16

	// 调用Windows API函数 sHGetSpecialFolderPathW 来获取特定目录的路径。
	// 参数说明：
	//   第一个参数为窗口句柄，0表示当前窗口。
	//   第二个参数为存放路径的数组的指针。
	//   第三个参数为标识符，指定需要获取的特定目录。
	//   第四个参数为标志，非0表示包含正在运行的文件。
	ret, _, _ := sHGetSpecialFolderPathW.X调用(0, uintptr(unsafe.Pointer(&path[0])), uintptr(X常量_系统目录_), 0)

	// 如果返回值为0，则表示获取路径失败，返回空字符串。
	if ret == 0 {
		return ""
	}

	// 将获取到的路径转换为字符串，去除末尾的空字符，并返回路径字符串。
	return strings.TrimRight(string(utf16.Decode(path[:])), "\x00")
}

// X取桌面目录 返回当前系统的桌面目录路径
// 返回值:字符串类型，表示桌面目录的路径
func X取桌面目录() string {
	// 调用系统_获取特定目录函数，传入X常量_系统目录_桌面参数，以获取桌面目录路径
	return X获取特定目录(X常量_系统目录_桌面)
}

// X取用户应用默认储存目录 返回用户的默认存储目录路径
// 返回值是一个字符串，表示用户应用的默认存储目录路径, 一般为:C:\Users\用户名\AppData\Local
func X取用户应用默认储存目录() string {
	// 调用X获取特定目录函数，参数为 X常量_系统目录_数据存储_Local，获取用户应用的默认存储目录
	return X获取特定目录(X常量_系统目录_数据存储_Local)
}

// X取system32目录获取系统32位目录路径
// 返回值：
// - string: system32目录的路径
// - error: 如果获取system32目录失败，则返回错误信息
func X取system32目录() (string, error) {
	// 调用windows.GetSystemDirectory()函数获取system32目录
	dir, err := windows.GetSystemDirectory()
	// 如果发生错误，则返回错误信息
	if err != nil {
		return "", fmt.Errorf("无法获取 system32 目录: %w", err)
	}
	// 返回system32目录路径
	return dir, nil
}

// X取Windows目录返回Windows系统目录的路径。
// 通过获取环境变量"SystemRoot"来确定Windows安装的根目录。
// 返回值是Windows系统的根目录路径，如果环境变量获取失败，则返回空字符串。
func X取Windows目录() string {
	return os.Getenv("SystemRoot")
}

// X取系统版本 获取系统名称、平台版本、内核版本号
// 返回值：
// - 系统名称: 如"Microsoft Windows 10"或"Microsoft Windows 11"
// - 平台版本: 如"10.0.19041.1 Build 19041.1"
// - 内核版本号: 如10
// - 错误: 在操作过程中可能遇到的错误
// 来源 https://github.com/shirou/gopsutil
func X取系统版本() (系统名称 string, 平台版本 string, 内核版本号 int, 错误 error) {
	return 内部类.X取系统版本()
}

// X取系统已安装软件 获取系统中已安装的软件列表
// 返回值是一个X已安装软件的切片和一个错误类型
// 来源 https://github.com/iamacarpet/go-win64api
func X取系统已安装软件() ([]X已安装软件, error) {
	// 获取64位注册表中的软件列表
	sw64, err := getSoftwareList(`SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall`, "", "X64")
	if err != nil {
		return nil, err
	}
	// 获取32位注册表中的软件列表
	sw32, err := getSoftwareList(`SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall`, "", "X32")
	if err != nil {
		return nil, err
	}
	// 打开注册表以获取用户列表
	k, err := registry.OpenKey(registry.USERS, "", registry.QUERY_VALUE|registry.ENUMERATE_SUB_KEYS)
	if err != nil {
		return nil, err
	}
	defer k.Close()

	// 读取注册表中的所有用户
	osUsers, err := k.ReadSubKeyNames(-1)
	if err != nil {
		return nil, err
	}

	// 使用map存储软件显示名称，以去重
	swmap := make(map[string]bool)
	// 初始化已安装软件列表
	var swList []X已安装软件

	// 遍历64位软件列表，添加到结果列表中
	for _, sw := range sw64 {
		if !swmap[sw.X显示名称] {
			swList = append(swList, sw)
			swmap[sw.X显示名称] = true
		}
	}
	// 遍历32位软件列表，添加到结果列表中
	for _, sw := range sw32 {
		if !swmap[sw.X显示名称] {
			swList = append(swList, sw)
			swmap[sw.X显示名称] = true
		}
	}

	// 遍历所有用户，获取每个用户的已安装软件列表
	for _, osUser := range osUsers {
		userSoftwareList64, err := getSoftwareList(`SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall`, osUser, "X64")
		if err == nil {
			// 遍历用户64位软件列表，添加到结果列表中
			for _, sw := range userSoftwareList64 {
				if !swmap[sw.X显示名称] {
					swList = append(swList, sw)
					swmap[sw.X显示名称] = true
				}
			}
		}
		userSoftwareList32, err := getSoftwareList(`SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall`, osUser, "X32")
		if err == nil {
			// 遍历用户32位软件列表，添加到结果列表中
			for _, sw := range userSoftwareList32 {
				if !swmap[sw.X显示名称] {
					swList = append(swList, sw)
					swmap[sw.X显示名称] = true
				}
			}
		}
		userDataSoftwareList64, err := getSoftwareList(`SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData`, osUser, "X64")
		if err == nil {
			// 遍历用户64位安装数据软件列表，添加到结果列表中
			for _, sw := range userDataSoftwareList64 {
				if !swmap[sw.X显示名称] {
					swList = append(swList, sw)
					swmap[sw.X显示名称] = true
				}
			}
		}
		userDataSoftwareList32, err := getSoftwareList(`SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Installer\UserData`, osUser, "X32")
		if err == nil {
			// 遍历用户32位安装数据软件列表，添加到结果列表中
			for _, sw := range userDataSoftwareList32 {
				if !swmap[sw.X显示名称] {
					swList = append(swList, sw)
					swmap[sw.X显示名称] = true
				}
			}
		}
	}
	// 返回已安装软件列表和无错误
	return swList, nil
}

// UAC是否已禁用 检查用户账户控制（UAC）是否已禁用。
// 注意:
// 1.需要管理员权限来执行此操作，并且只在支持UAC的Windows版本上可用。
// 2.重启后生效
// 返回值为true表示UAC已禁用，false表示UAC未禁用。
// 如果遇到错误，将返回false和一个描述错误的错误值。
func UAC是否已禁用() (bool, error) {
	// 尝试以管理员权限打开系统硬盘，来检测是否具有管理员权限。
	_, err := os.Open("\\\\.\\PHYSICALDRIVE0")
	if err != nil { // 如果打开失败，说明没有管理员权限
		log.Printf("UAC是否启用() 错误, 需要管理员权限运行.")
		return false, errors.New("UAC是否启用() 错误, 需要管理员权限运行.")
	}

	// 定义注册表路径，用于检查UAC设置。
	keyPath := `SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System`
	// 打开指定的注册表键项。
	表键, 错误 := 注册表类.X打开表项(注册表类.X常量_根路径_LOCAL_MACHINE, keyPath)
	if 错误 != nil {
		// 如果无法打开注册表键项，返回错误。
		log.Printf("UAC是否已禁用() 错误,无法打开注册表键:" + keyPath)
		return false, errors.New("UAC是否已禁用() 错误,无法打开注册表键:" + keyPath)
	}
	// 确保在函数返回前关闭注册表键项。
	defer 表键.X关闭()
	// 尝试获取UAC相关注册表值。
	值1, _, 错误 := 表键.X取整数值("ConsentPromptBehaviorAdmin")
	if 错误 != nil {
		// 如果无法读取注册表键项值，返回错误。
		log.Printf("UAC是否已禁用() 错误,无法读取注册表键:" + keyPath)
		return false, errors.New("UAC是否已禁用() 错误,无法读取注册表键:" + keyPath)
	}
	值2, _, 错误 := 表键.X取整数值("EnableLUA")
	if 错误 != nil {
		// 如果无法读取注册表键项值，返回错误。
		log.Printf("UAC是否已禁用() 错误,无法读取注册表键:" + keyPath)
		return false, errors.New("UAC是否已禁用() 错误,无法读取注册表键:" + keyPath)
	}
	// 根据获取的值判断UAC是否已禁用。
	return 值1 == 0 && 值2 == 0, nil
}

// X设置UAC状态 根据给定的状态参数设置Windows操作系统的用户账户控制（UAC）级别。
// 注意:
// 1.需要管理员权限来执行此操作，并且只在支持UAC的Windows版本上可用。
// 2.重启后生效
// 参数:
// 状态 - 要设置的UAC级别，通常为0-3，其中0表示直接提升UAC权限,不需要提示.
// 0 始终不通知,相当于禁用UAC
// 1 仅当应用尝试更改计算机时通知我(不降低桌面亮度)
// 2 仅当应用尝试更改我的计算机时通知我(默认)
// 3 始终通知
// 返回值:
// 如果函数成功设置UAC状态，则返回nil；否则返回一个错误。
func X设置UAC状态(状态 int64) error {
	// 尝试打开系统硬盘，用于检查是否具有管理员权限。
	_, err := os.Open("\\\\.\\PHYSICALDRIVE0")
	if err != nil { // 如果打开失败，说明没有管理员权限
		log.Printf("设置UAC状态() 错误, 需要管理员权限运行.")
		return errors.New("设置UAC状态() 错误, 需要管理员权限运行.")
	}

	// 定义UAC状态相关的注册表路径
	keyPath := `SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System`
	// 使用定义的路径打开注册表键
	表键, 错误 := 注册表类.X打开表项(注册表类.X常量_根路径_LOCAL_MACHINE, keyPath)
	if 错误 != nil {
		// 如果无法打开注册表键，返回错误
		log.Printf("设置UAC状态() 错误,无法打开注册表键:" + keyPath)
		return errors.New("设置UAC状态() 错误,无法打开注册表键:" + keyPath)
	}
	// 确保在函数返回前关闭注册表键
	defer 表键.X关闭()
	if 状态 == 0 {
		错误1 := 表键.X设置整数值_DWORD("ConsentPromptBehaviorAdmin", int32(0))
		错误2 := 表键.X设置整数值_DWORD("EnableLUA", int32(0))
		错误3 := 表键.X设置整数值_DWORD("PromptOnSecureDesktop", int32(0))
		if 错误1 != nil || 错误2 != nil || 错误3 != nil {
			log.Printf("设置UAC状态() 错误,无法设置注册表键:" + keyPath)
			return errors.New("设置UAC状态() 错误,无法设置注册表键:" + keyPath)
		}
	}
	if 状态 == 1 {
		错误1 := 表键.X设置整数值_DWORD("ConsentPromptBehaviorAdmin", int32(5))
		错误2 := 表键.X设置整数值_DWORD("EnableLUA", int32(1))
		错误3 := 表键.X设置整数值_DWORD("PromptOnSecureDesktop", int32(0))
		if 错误1 != nil || 错误2 != nil || 错误3 != nil {
			log.Printf("设置UAC状态() 错误,无法设置注册表键:" + keyPath)
			return errors.New("设置UAC状态() 错误,无法设置注册表键:" + keyPath)
		}
	}
	if 状态 == 2 {
		错误1 := 表键.X设置整数值_DWORD("ConsentPromptBehaviorAdmin", int32(5))
		错误2 := 表键.X设置整数值_DWORD("EnableLUA", int32(1))
		错误3 := 表键.X设置整数值_DWORD("PromptOnSecureDesktop", int32(1))
		if 错误1 != nil || 错误2 != nil || 错误3 != nil {
			log.Printf("设置UAC状态() 错误,无法设置注册表键:" + keyPath)
			return errors.New("设置UAC状态() 错误,无法设置注册表键:" + keyPath)
		}
	}
	if 状态 >= 3 {
		错误1 := 表键.X设置整数值_DWORD("ConsentPromptBehaviorAdmin", int32(2))
		错误2 := 表键.X设置整数值_DWORD("EnableLUA", int32(1))
		错误3 := 表键.X设置整数值_DWORD("PromptOnSecureDesktop", int32(1))
		if 错误1 != nil || 错误2 != nil || 错误3 != nil {
			log.Printf("设置UAC状态() 错误,无法设置注册表键:" + keyPath)
			return errors.New("设置UAC状态() 错误,无法设置注册表键:" + keyPath)
		}
	}
	return nil
}

// X添加Defender杀毒白名单 向Windows Defender添加指定目录或文件的杀毒白名单。
// 接受一个字符串参数，表示需要添加白名单的目录或文件路径。
// 返回一个布尔值，表示操作是否成功；和一个error，表示错误信息（如果有）。
// 注意:win7/8.1没有Defender,如果版本低于win10,那么直接返回成功.
func X添加Defender杀毒白名单(目录或文件 string) (bool, error) {
	// 检查当前程序是否以管理员权限运行
	if 进程类.X是否为管理员权限() == false {
		return false, errors.New("添加Defender杀毒白名单失败,错误: 请以管理员权限运行")
	}

	//win7/8.1没有Defender,如果版本低于win10,那么直接返回成功.
	_, _, 系统内核版本, _ := 内部类2.X取系统版本()
	if 系统内核版本 < 10 {
		return true, nil
	}

	// 构建 PowerShell 命令，用于添加Defender杀毒白名单
	cmd := exec.Command("powershell",
		`Add-MpPreference -ExclusionPath "`+目录或文件+`"`)

	// 隐藏 PowerShell 窗口
	cmd.SysProcAttr = &syscall.SysProcAttr{
		HideWindow: true,
	}

	// 执行命令
	err := cmd.Run()
	if err != nil {
		log.Println("添加Defender杀毒白名单失败,错误:", err)
		return false, err
	}
	return true, nil
}

func X文件是被占用(路径 string) bool {
	ptr, err := syscall.UTF16PtrFromString(路径)
	if err != nil {
		return true
	}

	// 使用 Windows API 尝试打开文件
	handle, err := syscall.CreateFile(
		ptr,
		syscall.GENERIC_READ,
		0, // 不共享访问权限，检查是否被其他进程占用
		nil,
		syscall.OPEN_EXISTING,
		syscall.FILE_ATTRIBUTE_NORMAL,
		0,
	)

	if err != nil {
		// 如果返回 "access denied" 或 "sharing violation" 错误，则文件被占用
		return true
	}

	// 关闭句柄
	syscall.CloseHandle(handle)
	return false
}

// 来源 https://github.com/timwhitez/unlock
// 注意,会杀掉相关进程.
func X文件强制解锁(filePath string) (int, error) {
	maxRetries := 5
	for i := 0; i < maxRetries; i++ {
		err := 内部_文件强制解锁(filePath)
		if err != nil {
			fmt.Printf("尝试%d未能解锁：%v\n", i+1, err)
			continue
		}

		data, err := os.ReadFile(filePath)
		if err == nil {
			return len(data), nil
		}
		fmt.Printf("尝试%d未能读取：%v\n", i+1, err)
	}
	return 0, fmt.Errorf("%d次尝试后未能获取数据", maxRetries)
}
